package yagwl.client.widget.internal;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

import yagwl.client.model.FilteredItemListCache;
import yagwl.client.model.DataReadyEventBus;
import yagwl.client.model.FilterDTO;
import yagwl.client.model.FilterEvent;
import yagwl.client.model.FilterEvent.FilterEventHandler;
import yagwl.client.model.ItemDTO;
import yagwl.client.resource.CellTableResources;
import yagwl.client.widget.Spacer;
import yagwl.client.widget.internal.Slider2Bar.LabelFormatter;

import com.google.gwt.cell.client.CheckboxCell;
import com.google.gwt.cell.client.FieldUpdater;
import com.google.gwt.cell.client.TextCell;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.client.ui.CellPanel;
import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.MultiSelectionModel;
import com.google.gwt.view.client.SelectionModel;
import com.google.gwt.view.client.SingleSelectionModel;

public class Filter extends Composite implements FilterEventHandler {

  private static final Logger log = Logger.getLogger(Filter.class.getName());
  private final SimplePanel rootSP = new SimplePanel();
  private final FilterSelectionChangeHandler filterSelectionChangeHandler;
  private FilteredItemListCache filteredListCache;
  private final List<FilterDTO> selectedFilterDTOs = new ArrayList<FilterDTO>();
  private Direction direction;
  private int minRowsForScroll;
  private String scrollHeight;

  public Filter(FilterSelectionChangeHandler filterSelectionChangeHandler) {
    this.filterSelectionChangeHandler = filterSelectionChangeHandler;
    initWidget(rootSP);
    // register self as a listener of data ready events
    DataReadyEventBus.getInstance().addHandler(FilterEvent.TYPE, this);
  }

  public void setFilteredListCache(FilteredItemListCache filteredListCache) {
    this.filteredListCache = filteredListCache;
    filteredListCache.fetchFilters();
  }

  @Override
  public void onFilterEvent(FilterEvent event) {
    log.info("onFilterEvent: Entering");
    //if (event.getFilteredListCache() == null)
    //  return;
    String eventName = event.getFilteredListCache().getClass().getName();
    String objName = filteredListCache.getClass().getName();
    if (objName != eventName)
      return;
    log.info("onDataReady: ItemListEvent caught: " + eventName);
    log.info("onDataReady: rendering filters");
    renderUI();
  }

  public void renderUI() {
    if (filteredListCache.getFilters() == null)
      return;

    CellPanel filtersPanel = direction == Direction.Horizontal ? new HorizontalPanel()
        : new VerticalPanel();
    rootSP.setWidget(filtersPanel);

    List<FilterDTO> filterList = filteredListCache.getFilters();
    for (FilterDTO filterDTO : filterList) {
      renderFilter(filtersPanel, filterDTO);
      if (direction == Direction.Horizontal)
        filtersPanel.add(new Spacer(10, 1));
    }
  }
  
  // save filter widgets to be re-used in case orientation is switched
  private Map<String, VerticalPanel> filterWidgets = new HashMap<String, VerticalPanel>();

  private void renderFilter(CellPanel filtersPanel, FilterDTO filterDTO) {
    log.info("Rendering: " + filterDTO.getId());
    VerticalPanel vPanel = filterWidgets.get(filterDTO.getId());
    if (vPanel != null) { // orientation is being switched
      filtersPanel.add(vPanel);
      return;
    }
    vPanel = new VerticalPanel();
    filterWidgets.put(filterDTO.getId(), vPanel);
    vPanel.setWidth("100%");
    filtersPanel.add(vPanel);
    Label header = new Label(filterDTO.getLabel());
    header.setStyleName("ya-filterTable-filter-header");
    vPanel.add(header);

    switch (filterDTO.getFilterType()) {
    case MultiSelect:
      SelectionModel<ItemDTO> selectionModel = new MultiSelectionModel<ItemDTO>();
      renderSelectFilter(vPanel, filterDTO, selectionModel);
      break;
    case SingleSelect:
      selectionModel = new SingleSelectionModel<ItemDTO>();
      renderSelectFilter(vPanel, filterDTO, selectionModel);
      break;
    case IntegerRange:
      renderIntegerRangeFilter(vPanel, filterDTO);
    }
  }

  private void renderSelectFilter(VerticalPanel parent, final FilterDTO filterDTO,
      final SelectionModel<ItemDTO> selectionModel) {
    CellTable<ItemDTO> cellTable = new CellTable<ItemDTO>(200,
        CellTableResources.R);
    cellTable.setStyleName("ya-filterTable-filter");
    cellTable.setSelectionModel(selectionModel);

    // initialize checkbox column
    Column<ItemDTO, Boolean> checkColumn = new Column<ItemDTO, Boolean>(
        new CheckboxCell(true)) {
      @Override
      public Boolean getValue(ItemDTO object) {
        // Get the value from the selection model.
        return selectionModel.isSelected(object);
      }
    };
    checkColumn.setFieldUpdater(new FieldUpdater<ItemDTO, Boolean>() {
      public void update(int index, ItemDTO object, Boolean value) {
        // Called when the user clicks on a checkbox.
        selectionModel.setSelected(object, value);
        if (selectionModel instanceof MultiSelectionModel) {
          Set<ItemDTO> set = ((MultiSelectionModel<ItemDTO>) selectionModel)
              .getSelectedSet();
          if (set.size() > 0)
            getSelectedFilter(filterDTO.getId()).setMultiSelectList(
                new ArrayList<ItemDTO>(set));
          else
            removeSelectedFilter(filterDTO.getId());
        }
        if (selectionModel instanceof SingleSelectionModel) {
          ItemDTO item = ((SingleSelectionModel<ItemDTO>) selectionModel)
              .getSelectedObject();
          if (item != null) {
            List<ItemDTO> itemlist = new ArrayList<ItemDTO>();
            itemlist.add(item);
            getSelectedFilter(filterDTO.getId()).setSingleSelectList(itemlist);
          } else
            removeSelectedFilter(filterDTO.getId());
        }
        filterSelectionChangeHandler.onChange(selectedFilterDTOs);
      }
    });
    cellTable.addColumn(checkColumn);

    // initialize label column
    Column<ItemDTO, String> labelColumn = new Column<ItemDTO, String>(
        new TextCell()) {
      @Override
      public String getValue(ItemDTO object) {
        return object.getLabel();
      }
    };
    cellTable.addColumn(labelColumn);

    // push data into cell table
    cellTable.setRowData(0, filterDTO.getSelectList());

    if (filterDTO.getSelectList().size() > minRowsForScroll) {
      ScrollPanel scroll = new ScrollPanel(cellTable);
      parent.add(scroll);
      scroll.setHeight(scrollHeight);
    } else
      parent.add(cellTable);
  }

  private void renderIntegerRangeFilter(VerticalPanel parent,
      final FilterDTO filterDTO) {
    int numLabels = (filterDTO.getMaxValue() - filterDTO.getMinValue())
        / filterDTO.getStepSize();
    int numTicks = numLabels;
    while (numLabels > 8)
      numLabels /= 2;
    final Slider2Bar slider = new Slider2Bar(filterDTO.getMinValue(),
        filterDTO.getMaxValue(), filterDTO.getStepSize(), 200, numLabels,
        numTicks, true, new LabelFormatter() {

          @Override
          public String formatLabel(Slider2Bar slider, double value) {
            return "" + (int) value;
          }

        });
    slider.setNotifyOnMouseMove(false);
    slider.drawAll();

    slider.addChangeListener(new ChangeListener() {

      @Override
      public void onChange(Widget sender) {
        int current1Value = (int) slider.getCurrent1Value();
        int current2Value = (int) slider.getCurrent2Value();
        if (current1Value == filterDTO.getMinValue()
            && current2Value == filterDTO.getMaxValue())
          removeSelectedFilter(filterDTO.getId());
        else
          getSelectedFilter(filterDTO.getId()).setIntegerRange(current1Value,
              current2Value, 0);
        filterSelectionChangeHandler.onChange(selectedFilterDTOs);
      }

    });
    parent.add(slider);
  }

  private FilterDTO getSelectedFilter(String filterId) {
    for (FilterDTO filter : selectedFilterDTOs)
      if (filter.getId().equals(filterId))
        return filter;
    FilterDTO filter = new FilterDTO();
    filter.setId(filterId);
    selectedFilterDTOs.add(filter);
    return filter;
  }

  private void removeSelectedFilter(String filterId) {
    for (FilterDTO filter : selectedFilterDTOs)
      if (filter.getId().equals(filterId)) {
        selectedFilterDTOs.remove(filter);
        return;
      }
  }

  public interface FilterSelectionChangeHandler {
    public void onChange(List<FilterDTO> selectedFilters);
  }

  public void setMinRowsAndHeightForScroll(int minRows, String height) {
    this.minRowsForScroll = minRows;
    this.scrollHeight = height;
  }

  public void setDirection(Direction direction) {
    this.direction = direction;
  }

  public enum Direction {
    Horizontal, Vertical
  }
}
